import * as THREE from "three"
import { DisableCursorMeshCommand, SetMouseCursorCommand } from "../command/cursor.command"
import { MeshPreviewSetPositonCommand } from "../command/mesh.command"
import { LockNavigationCommand, UnlockNavigationCommand } from "../command/navigate.command"
import { OpenNoteCommand, PreviewNoteCommand } from "../command/note.command"
import {
  AdjustPinStemLengthCommand,
  CancelNewPinCommand,
  ClickAwayFromPinCommand,
  EnablePinEditingCommand,
  MovePinCommand,
  OpenPinCommand,
  PlacePinCommand,
  PreviewPinCommand,
  SelectPinCommand
} from "../command/pin.command"
import { DisableSweepSelectionCommand, EnableSweepSelectionCommand } from "../command/sweep.command"
import { OpenTagCommand, PreviewTagCommand, SelectTagCommand } from "../command/tags.command"
import configConst from "../constant/config.const"
import ApplicationData from "../data/ApplicationData"
import CameraData from "../data/CameraData"
import CanvasData from "../data/CanvasData"
import FloorsData from "../data/FloorsData"
import FloorsViewData from "../data/FloorsViewData"
import InteractionmodeData from "../data/InteractionmodeData"
import PinsViewData from "../data/PinsViewData"
import SweepData from "../data/SweepData"
import ViewmodeData from "../data/ViewmodeData"
import EngineContext from "../EngineContext"
import { appTypeMin } from "../enum/appType.enum"
import cursorEnum from "../enum/cursor.enum"
import hotKeyEnum, { KeyState } from "../enum/hotKey.enum"
import pinColorEnum from "../enum/pinColor.enum"
import pinStateEnum from "../enum/pinState.enum"
import pinTypeEnum from "../enum/pinType.enum"
import transitionTypeEnum from "../enum/transitionType.enum"
import viewmodeEnum from "../enum/viewmode.enum"
import { DragEndEvent, DragEvent } from "../event/DragEvent"
import { ClickEvent, LongPressEndEvent, LongPressStartEvent } from "../event/GestureEvent"
import { KeyEvent } from "../event/KeyEvent"
import { PointerButtonEvent, PointerMoveEvent } from "../event/PointerEvent"
import a from "../math/27687"
import ModelColliderTarget from "../mesh/ModelColliderTarget"
import PinAnchorMesh from "../mesh/PinAnchorMesh"
import PinHeadMesh from "../mesh/PinHeadMesh"
import SkySphereMesh from "../mesh/SkySphereMesh"
import { ApplicationChangeMessage } from "../message/app.message"
import {
  FocusPinAnnotationMessage,
  PinAddCancelledMessage,
  PinClickedMessage,
  PinClosedMessage,
  PinDeletedMessage,
  PinDeselectMessage,
  PinDiscardedMessage,
  PinHoverChangeMessage,
  PinMovedMessage,
  PinOpenedMessage,
  PinPlacedMessage,
  PinSelectedMessage,
  PinStartCreatingMessage,
  PinUpdatedMessage
} from "../message/pin.message"
import { MoveToSweepMessage } from "../message/sweeps.messge"
import { ToolAssetOpenedMessage, ToolToggledMessage } from "../message/tool.message"
import { EndSwitchViewmodeMessage, SwitchViewmodeMessage } from "../message/viewmode.message"
import AnimatedNumber from "../object/AnimatedNumber"
import Mattertag from "../object/Mattertag"
import B from "../packages/76870"
import InstancedPinRenderer from "../renderer/InstancedPinRenderer"
import NonInstancedPinRenderer from "../renderer/NonInstancedPinRenderer"
import SymbolList from "../SymbolList"
import Filter from "../three/Filter"
import AggregateSubscription from "../utils/AggregateSubscription"
import browserUtil from "../utils/browser.util"
import Comparator from "../utils/Comparator"
import funcUtil from "../utils/func.util"
import Logger from "../utils/Logger"
import { roomFunc } from "../utils/room.util"
import { Subscription } from "../utils/Subscription"
import FatcasterModule from "./FatcasterModule"
import InputIniModule from "./InputIniModule"
import Module from "./Module"
import RaycasterModule from "./RaycasterModule"
import WebGLRendererModule from "./WebGLRendererModule"

class PinEditor {
  viewData: PinsViewData
  engine: EngineContext
  input: InputIniModule
  pinRenderer: InstancedPinRenderer | NonInstancedPinRenderer
  bindings: Partial<Subscription>[]
  externalBehaviorsBlocked: boolean
  touchDevice: boolean
  longPressCreateThreshold: number
  ndcPoint: THREE.Vector3
  movingPin: boolean
  anchored: boolean
  inAnchorClick: boolean
  log: Logger
  creatingHandlers: AggregateSubscription
  draggingHandlers: AggregateSubscription
  selectedHandlers: AggregateSubscription
  longPressStart: number
  longPressTimeout: number
  dragInterceptor: AggregateSubscription
  fatcaster: FatcasterModule
  floorsData: FloorsData
  cameraData: CameraData

  constructor(e, t, i, s) {
    this.viewData = e
    this.engine = t
    this.input = i
    this.pinRenderer = s
    this.bindings = []
    this.externalBehaviorsBlocked = !1
    this.touchDevice = browserUtil.canTouch()
    this.longPressCreateThreshold = 500
    this.ndcPoint = new THREE.Vector3()
    this.movingPin = !1
    this.anchored = !1
    this.inAnchorClick = !1
    this.log = new Logger("note-editor")
    Promise.all([t.market.waitForData(CameraData), t.market.waitForData(FloorsData), t.getModule(FatcasterModule)]).then(([e, t, i]) => {
      this.cameraData = e
      this.floorsData = t
      this.fatcaster = i
      this.registerHandlers()
    })
  }
  stopEventPropagation() {
    return true
  }
  async updateMeshPreviewSphere(e, t?) {
    this.engine.commandBinder.issueCommand(new MeshPreviewSetPositonCommand(e, t))
  }
  startPinCreation() {
    this.creatingHandlers.renew()
    this.touchDevice || this.engine.commandBinder.issueCommand(new SetMouseCursorCommand(cursorEnum.XHAIR))
    this.engine.commandBinder.issueCommand(new DisableSweepSelectionCommand())
  }
  endPinCreation() {
    this.draggingHandlers.cancel()
    this.creatingHandlers.cancel()
    this.clearLongPressTimeout()
    this.allowExternalBehaviors(!0)
    this.engine.commandBinder.issueCommand(new SetMouseCursorCommand(cursorEnum.DEFAULT))
    this.engine.commandBinder.issueCommand(new EnableSweepSelectionCommand())
  }
  onOpenPinChanged() {
    this.updateAnchorMesh()
  }
  onPinStateUpdated() {
    const e = this.viewData.pinState
    switch ((this.updateAnchorMesh(), e)) {
      case pinStateEnum.PLACING:
        this.draggingHandlers.renew()
        break
      case pinStateEnum.IDLE:
        this.allowExternalBehaviors(!0)
    }
  }
  updateAnchorMesh() {
    const { openPin: e, pinState: t } = this.viewData
    e
      ? t === pinStateEnum.SELECTED
        ? this.selectedHandlers.renew()
        : [pinStateEnum.PLACING, pinStateEnum.PLACED, pinStateEnum.EDITING].includes(t) &&
          (this.pinRenderer.showAnchorMesh(e.pinType, e.id, e), (this.anchored = !0), this.selectedHandlers.renew())
      : (this.selectedHandlers.cancel(), this.anchored && (this.removePinAnchorMesh(), this.pinRenderer.hideSelectedMesh(), this.updateMeshPreviewSphere(!1)))
  }
  clearLongPressTimeout() {
    this.longPressStart = 0
    this.longPressTimeout !== -1 && window.clearTimeout(this.longPressTimeout)
    this.longPressTimeout = -1
  }
  placePin() {
    const e = this.viewData
    e.pinState === pinStateEnum.PLACING &&
      (e.setPinState(pinStateEnum.PLACED),
      this.engine.commandBinder.issueCommand(new SetMouseCursorCommand(cursorEnum.DEFAULT)),
      this.engine.commandBinder.issueCommand(new PlacePinCommand()),
      this.engine.broadcast(new FocusPinAnnotationMessage()),
      this.updateMeshPreviewSphere(!1))
  }
  onDragEvent(e) {
    ;(e.buttons !== B.r3.PRIMARY && (this.touchDevice || this.viewData.openPin)) || this.positionPin(e)
  }
  onDragEnd(e) {
    const { openPin: t, creatingNewPin: i, pinState: n } = this.viewData
    if ((!i || this.touchDevice || n !== pinStateEnum.PLACING) && t && !i) {
      const e = {
        anchorPosition: t.anchorPosition,
        stemNormal: t.stemNormal,
        floorId: t.floorId,
        roomId: t.roomId,
        stemLength: t.stemLength,
        stemEnabled: t.stemEnabled,
        color: t.color
      }
      this.engine.commandBinder.issueCommand(new MovePinCommand(t.id, e)), this.updateMeshPreviewSphere(!1)
    }
  }
  async allowExternalBehaviors(e) {
    e || this.externalBehaviorsBlocked
      ? e &&
        this.externalBehaviorsBlocked &&
        (this.dragInterceptor.cancel(), this.engine.commandBinder.issueCommand(new UnlockNavigationCommand()), (this.externalBehaviorsBlocked = !1))
      : (this.dragInterceptor.renew(), this.engine.commandBinder.issueCommand(new LockNavigationCommand()), (this.externalBehaviorsBlocked = !0))
  }
  tmpOv = new THREE.Vector3()
  async positionPin(t) {
    if (this.touchDevice && t.buttons !== B.r3.PRIMARY) return !1
    const i = this.viewData,
      n = i.pinState
    if (n === pinStateEnum.CREATING) this.engine.commandBinder.issueCommand(new DisableCursorMeshCommand(!0))
    else if (n !== pinStateEnum.PLACING && !this.movingPin) return !1
    const s = i.openPin
    if (!s) return !1
    this.saveScreenPosition(t.position.x, t.position.y)
    const r = this.getModelIntersection()
    if (r && r.face) {
      this.engine.commandBinder.issueCommand(new SetMouseCursorCommand(cursorEnum.XHAIR)), i.setCanPlace(!0)
      const t = ((e, t) => (roomFunc.hasFloorId(t.object) ? t.object.floorId : e.getClosestFloorAtHeight(t.point.y).id))(this.floorsData, r)
      if (null === t) return !1
      const o = roomFunc.hasRoomId(r.object) ? r.object.roomId : void 0
      this.tmpOv.copy(s.stemNormal).setLength(s.stemLength)
      this.updateMeshPreviewSphere(!0, s.anchorPosition)
      i.updateOpenPin({
        anchorPosition: s.anchorPosition.copy(r.point),
        stemNormal: s.stemNormal.copy(r.face.normal).normalize(),
        floorId: t,
        roomId: o
      })
      n === pinStateEnum.CREATING && i.setPinState(pinStateEnum.PLACING)
      return !0
    }
    this.engine.commandBinder.issueCommand(new SetMouseCursorCommand(cursorEnum.NOPE))
    i.setCanPlace(!1)
    return !1
  }
  getModelIntersection() {
    const e = 0.5 * configConst.PlaneBufferGeometryConfg.anchor.size
    return this.fatcaster.cast(e, funcUtil.traverseAncestorsVisible, Filter.CENTER_GROUP(e))
  }
  onPointerButton(e) {
    e.down ||
      this.viewData.pinState !== pinStateEnum.PLACED ||
      (this.allowExternalBehaviors(!0), this.engine.commandBinder.issueCommand(new DisableCursorMeshCommand(!1)))
  }
  onAnchorSelect(e) {
    const t = this.viewData
    if (!t.openPin || e.button !== B.MP.PRIMARY) return
    t.pinState === pinStateEnum.EDITING &&
      (e.down
        ? ((this.movingPin = !0),
          this.allowExternalBehaviors(!1),
          this.draggingHandlers.renew(),
          this.engine.commandBinder.issueCommand(new SetMouseCursorCommand(cursorEnum.GRABBING)),
          this.engine.commandBinder.issueCommand(new DisableCursorMeshCommand(!0)),
          (this.inAnchorClick = !0))
        : (this.doneMovingPin(), (this.inAnchorClick = !1)))
  }
  doneMovingPin() {
    this.viewData.openPin &&
      this.movingPin &&
      (this.draggingHandlers.cancel(),
      this.engine.commandBinder.issueCommand(new SetMouseCursorCommand(null)),
      this.engine.commandBinder.issueCommand(new DisableCursorMeshCommand(!1)),
      (this.movingPin = !1),
      this.allowExternalBehaviors(!0),
      this.updateMeshPreviewSphere(!1))
  }
  onClickElsewhere(e) {
    const { openPin: t, pinState: i } = this.viewData
    if (!((i === pinStateEnum.CREATING && t) || this.inAnchorClick))
      return t && (this.movingPin ? this.doneMovingPin() : this.engine.commandBinder.issueCommand(new ClickAwayFromPinCommand())), !0
    return null
  }
  async onLongPressStart(e) {
    if (e.buttons !== B.r3.PRIMARY) return
    const t = this.viewData
    t.pinState === pinStateEnum.CREATING &&
      ((this.longPressStart = Date.now()),
      t.setPinState(pinStateEnum.PRESSING),
      this.allowExternalBehaviors(!1),
      this.saveScreenPosition(e.position.x, e.position.y),
      (this.longPressTimeout = window.setTimeout(async () => {
        t.setPinState(pinStateEnum.PLACING)
        ;(await this.positionPin(e)) || (t.setPinState(pinStateEnum.CREATING), t.setCanPlace(!1)), (this.longPressTimeout = -1)
      }, this.longPressCreateThreshold)))
  }
  onLongPressEnd() {
    const e = this.viewData.pinState
    e === pinStateEnum.PRESSING ? (this.log.debug("Did not press long enough"), this.endPinCreation()) : e === pinStateEnum.PLACING && this.placePin()
  }
  onPointerEvent(e) {
    const t = this.viewData.pinState
    ;(t !== pinStateEnum.PLACING && t !== pinStateEnum.CREATING) || this.positionPin(e)
  }
  onClickToPlacePin() {
    this.placePin()
  }
  onKeyEvent(e) {
    if (e.state === KeyState.PRESSED)
      switch (e.key) {
        case hotKeyEnum.ESCAPE:
          this.viewData.creatingNewPin && this.engine.commandBinder.issueCommand(new CancelNewPinCommand())
      }
  }
  dispose() {
    this.removePinAnchorMesh(),
      this.updateMeshPreviewSphere(!1),
      this.dragInterceptor.cancel(),
      this.creatingHandlers.cancel(),
      this.draggingHandlers.cancel(),
      this.selectedHandlers.cancel(),
      this.bindings.forEach(e => {
        e.cancel()
      })
  }
  update() {
    const e = this.viewData
    if (this.touchDevice && e.pinState === pinStateEnum.PRESSING) {
      const t = Math.min(1, (Date.now() - this.longPressStart) / this.longPressCreateThreshold)
      e.setProgress(t)
    }
  }
  registerHandlers() {
    const e = this.input
    this.bindings.push(
      this.viewData.onPinStateChanged(this.onPinStateUpdated),
      this.viewData.onOpenPinChanged(this.onOpenPinChanged),
      e.registerPriorityHandler(PointerButtonEvent, PinHeadMesh, this.stopEventPropagation)
    ),
      (this.dragInterceptor = new AggregateSubscription(
        e.registerPriorityHandler(DragEvent, ModelColliderTarget, () => !0),
        e.registerPriorityHandler(DragEvent, SkySphereMesh, () => !0)
      )),
      this.dragInterceptor.cancel(),
      (this.draggingHandlers = new AggregateSubscription(
        e.registerUnfilteredHandler(DragEvent, this.onDragEvent),
        e.registerUnfilteredHandler(DragEndEvent, this.onDragEnd)
      )),
      this.draggingHandlers.cancel(),
      this.touchDevice
        ? ((this.creatingHandlers = new AggregateSubscription(
            e.registerHandler(PointerButtonEvent, this.onPointerButton),
            e.registerUnfilteredHandler(LongPressStartEvent, this.onLongPressStart),
            e.registerUnfilteredHandler(LongPressEndEvent, this.onLongPressEnd)
          )),
          (this.selectedHandlers = new AggregateSubscription(
            e.registerMeshHandler(PointerButtonEvent, Comparator.isType(PinAnchorMesh), this.onAnchorSelect),
            e.registerPriorityHandler(ClickEvent, ModelColliderTarget, this.onClickElsewhere),
            e.registerPriorityHandler(ClickEvent, SkySphereMesh, this.onClickElsewhere)
          )))
        : ((this.creatingHandlers = new AggregateSubscription(
            e.registerHandler(KeyEvent, this.onKeyEvent),
            e.registerHandler(PointerButtonEvent, this.onPointerButton),
            e.registerUnfilteredHandler(ClickEvent, this.onClickToPlacePin),
            e.registerHandler(PointerMoveEvent, this.onPointerEvent)
          )),
          (this.selectedHandlers = new AggregateSubscription(
            e.registerMeshHandler(PointerButtonEvent, Comparator.isType(PinAnchorMesh), this.onAnchorSelect),
            e.registerPriorityHandler(ClickEvent, ModelColliderTarget, this.onClickElsewhere),
            e.registerPriorityHandler(ClickEvent, SkySphereMesh, this.onClickElsewhere)
          ))),
      this.creatingHandlers.cancel(),
      this.selectedHandlers.cancel()
  }
  removePinAnchorMesh() {
    this.pinRenderer.hideAnchorMesh()
    this.anchored = !1
  }
  saveScreenPosition(e, t) {
    this.ndcPoint.set(e, t, 0)
    const i = a.fi(this.cameraData.width, this.cameraData.height, this.ndcPoint)
    this.viewData.setScreenPosition(i)
  }
}

export type matterItemData = Mattertag & {
  stemNormal: THREE.Vector3
  stemLength: number
  stemEnabled: boolean
  color: string
}
export type pinItemData = matterItemData & {
  visible: boolean
  opacity: number
  pinType: string
  opacityAnimation: AnimatedNumber
  backgroundTexture: THREE.Texture
  maskTexture: THREE.Texture
  pinHeadMatrix: THREE.Matrix4
  pinHeadObjPosition: THREE.Vector3
  pinColor: THREE.Color
  colorVariant: string
  overrideTexture: THREE.Texture
  geomScale: number
}

export default class PinsModule extends Module {
  editActivated: boolean
  editBindings: Partial<Subscription>[]
  touchDevice: boolean
  worldPosition: THREE.Vector3
  visibilityChanged: () => void
  interactionmodeData: InteractionmodeData
  pinRenderer: InstancedPinRenderer | NonInstancedPinRenderer
  viewmode: number
  viewmodeChanged: any
  onEnablePinEditing: any
  applicationChanged: any
  pinEditor: PinEditor
  updateRenderedPin: () => void
  viewData: PinsViewData
  onPinUpdated: any
  onPinSelected: any
  onPinClosed: any
  onPinOpened: any
  onStartPinCreation: any
  onPreviewPinAnnotation: any
  onOpenPin: (e: any) => Promise<any>
  tagsEnabled: boolean
  onSelectPin: any
  onPreviewStemLength: any
  onCameraUpdate: () => void
  onAssetOpened: any
  onToolToggled: any
  handleSweepChange: () => void
  handleViewModeChange: () => void
  cancelAndReset: () => void
  engine: EngineContext
  cancelPinCreation: () => Promise<void>
  onPinDeleted: any
  onPinClicked: any
  onHoverChanged: any
  clickOffPin: () => Promise<void>
  movePin: any
  placePin: any
  input: InputIniModule
  floorsViewData: FloorsViewData
  viewmodeData: ViewmodeData
  sweepData: SweepData
  saveScreenPosition: any
  resetState: any
  cameraData: CameraData
  constructor() {
    super(...arguments)
    this.name = "pins"
    this.editActivated = !1
    this.editBindings = []
    this.touchDevice = browserUtil.canTouch()
    this.worldPosition = new THREE.Vector3()
    this.visibilityChanged = () => {
      const e = !this.in360View(),
        t = !this.interactionmodeData.isVR()
      this.pinRenderer.container.visible = e && t
      const { floorsViewData: i } = this,
        n = this.viewmode === viewmodeEnum.Dollhouse || this.viewmode === viewmodeEnum.Floorplan,
        s = i.transition.progress.active ? () => !0 : i.isHidden
      this.pinRenderer.setFloorsHidden(n ? s : () => !1)
    }
    this.viewmodeChanged = e => {
      this.viewmode = e.toMode
      this.visibilityChanged()
    }
    this.onEnablePinEditing = async e => {
      e.enabled ? this.enableEditing() : this.disableEditing()
    }
    this.applicationChanged = e => {
      this.pinEditor && (e.application, appTypeMin.WORKSHOP)
    }
    this.updateRenderedPin = () => {
      const { pinState, creatingNewPin, openPin, previewPin } = this.viewData
      if (!creatingNewPin || [pinStateEnum.PLACING, pinStateEnum.PLACED, pinStateEnum.EDITING].includes(pinState)) {
        this.editActivated && this.pinEditor.updateAnchorMesh()
        const pin = previewPin || openPin
        if (pin) {
          const { id, pinType, backgroundTexture, maskTexture } = pin
          this.pinRenderer.updatePin(id, pinType, pin, backgroundTexture, maskTexture)
          this.pinRenderer.setPinColorVariant(id, pinColorEnum.HIGHLIGHTED)
          this.pinRenderer.setPinColorVariants(pinColorEnum.DEFAULT, id)
        } else {
          this.pinRenderer.setPinColorVariants(pinColorEnum.DEFAULT)
        }
      }
    }
    this.onPinUpdated = e => {
      const t = Object.assign(Object.assign({}, e.pin), {
        pinType: e.pinType
      })
      this.viewData.updateOpenPin(t), this.saveScreenPosition(t)
    }
    this.onPinSelected = e => {
      const t = Object.assign(Object.assign({}, e.pin), {
          pinType: e.pinType
        }),
        i = this.viewData
      i.setPinState(pinStateEnum.SELECTED), i.setOpenPin(t), this.saveScreenPosition(t)
    }
    this.onPinClosed = e => {
      const { pinType: t, id: i } = e,
        { openPin: n, previewPin: s } = this.viewData,
        r = i === (null == n ? void 0 : n.id) ? n : i === (null == s ? void 0 : s.id) ? s : null
      r &&
        r.pinType === t &&
        r.id === i &&
        (this.resetState(),
        r.id === (null == n ? void 0 : n.id) ? this.viewData.setOpenPin(null) : r.id === (null == s ? void 0 : s.id) && this.viewData.setPreviewPin(null))
    }
    this.onPinOpened = e => {
      const t = this.viewData,
        i = Object.assign(Object.assign({}, e.pin), {
          pinType: e.pinType
        })
      t.setEditablePin(e.editable), t.setPinState(e.editable ? pinStateEnum.EDITING : pinStateEnum.OPEN), t.setOpenPin(i), this.saveScreenPosition(i)
    }
    this.onStartPinCreation = e => {
      const t = this.viewData,
        i = Object.assign(Object.assign({}, e.pin), {
          pinType: e.pinType
        })
      t.setEditablePin(!0), t.setPinState(pinStateEnum.CREATING), t.setOpenPin(i), this.pinEditor.startPinCreation()
    }
    this.onPreviewPinAnnotation = async e => {
      const t = this.viewData,
        i = Object.assign(Object.assign({}, e.pin), {
          pinType: e.pinType
        })
      t.pinState === pinStateEnum.SELECTED && t.setOpenPin(null), t.setPinState(pinStateEnum.PREVIEWING), t.setPreviewPin(i), this.saveScreenPosition(i)
    }
    this.onOpenPin = async e => {
      if (e.pinType !== pinTypeEnum.MATTERTAG || this.tagsEnabled) return this.openPin(e.id, e.pinType, e.transition, e.attachmentId, e.listIndex)
    }
    this.onSelectPin = async e => {
      ;(e.pinType !== pinTypeEnum.MATTERTAG || this.tagsEnabled) && this.selectPin(e.id, e.pinType, e.transition)
    }
    this.onPreviewStemLength = async e => {
      const { openPin: t, isPinEditable: i } = this.viewData
      i && t && t.id === e.id ? this.viewData.updateOpenPin({ stemLength: e.stemLength }) : this.log.debug("Cannot preview stem length, not open for edit")
    }
    this.saveScreenPosition = (() => {
      const e = new THREE.Vector3()
      return t => {
        const i = this.viewData
        e.copy(t.stemNormal).setLength(t.stemLength), this.worldPosition.copy(t.anchorPosition).add(e)
        const n = a.q9(this.cameraData, this.worldPosition)
        i.setScreenPosition(n.screenPosition)
      }
    })()
    this.onCameraUpdate = () => {
      const { creatingNewPin: e, previewPin: t, openPin: i } = this.viewData
      !e && t ? this.saveScreenPosition(t) : i && this.saveScreenPosition(i)
    }
    this.onAssetOpened = e => {
      this.viewData.setPreviewPin(null)
    }
    this.onToolToggled = e => {
      e.open || this.resetState()
    }
    this.handleSweepChange = () => this.handleSweepAndViewModeChange()
    this.handleViewModeChange = () => this.handleSweepAndViewModeChange()
    this.cancelAndReset = () => {
      const { creatingNewPin: e, openPin: t } = this.viewData
      e && t && (this.pinRenderer.removePin(t.id), this.engine.broadcast(new PinAddCancelledMessage(t.id, t.pinType))),
        this.viewData.setOpenPin(null),
        this.resetState()
    }
    this.resetState = () => {
      const e = this.viewData
      e.setEditablePin(!1),
        e.setCanPlace(!0),
        e.setScreenPosition(null),
        e.setPinState(pinStateEnum.IDLE),
        e.setCanAdd(!this.in360View()),
        this.pinRenderer.hideSelectedMesh()
    }
    this.cancelPinCreation = async () => {
      this.cancelAndReset()
    }
    this.onPinDeleted = e => {
      const { pinType: t } = e
      if (t === pinTypeEnum.MATTERTAG && !this.tagsEnabled) return
      this.pinRenderer.removePin(e.id)
      const { openPin: i } = this.viewData
      ;(null == i ? void 0 : i.id) === e.id && (null == i ? void 0 : i.pinType) === e.pinType && this.cancelAndReset()
    }
    this.onPinClicked = e => {
      const { pinType: t, id: i } = e
      if (t !== pinTypeEnum.MATTERTAG || this.tagsEnabled)
        if (t === pinTypeEnum.MATTERTAG) this.selectPin(i, t, transitionTypeEnum.Interpolate)
        else {
          const { openPin: e } = this.viewData,
            n = i === (null == e ? void 0 : e.id) && t === (null == e ? void 0 : e.pinType)
          e && this.deselectPin(e), n || this.openPin(i, t, transitionTypeEnum.Interpolate)
        }
    }
    this.onHoverChanged = e => {
      const { pinType: t, id: i, hovering: n } = e
      if (t === pinTypeEnum.MATTERTAG && !this.tagsEnabled) return
      const { creatingNewPin: s, pinState: r, previewPin: o, openPin: a, isPinEditable: l } = this.viewData
      if (!s)
        if (n) {
          if (a) {
            if (a.id === i && a.pinType === t) return
            r === pinStateEnum.SELECTED && (this.viewData.setOpenPin(null), this.pinRenderer.hideSelectedMesh())
          }
          t === pinTypeEnum.MATTERTAG
            ? this.engine.commandBinder.issueCommand(new PreviewTagCommand(i))
            : t === pinTypeEnum.NOTE && this.engine.commandBinder.issueCommand(new PreviewNoteCommand(i)),
            this.pinRenderer.setPinColorVariantByType(t, pinColorEnum.DEFAULT, null == o ? void 0 : o.id),
            this.pinRenderer.setPinColorVariant(i, pinColorEnum.HIGHLIGHTED)
        } else {
          const e = r === pinStateEnum.PREVIEWING
          o &&
            e &&
            (this.pinRenderer.setPinColorVariant(o.id, pinColorEnum.DEFAULT),
            this.viewData.setPinState(a ? (l ? pinStateEnum.EDITING : pinStateEnum.OPEN) : pinStateEnum.IDLE),
            this.viewData.setPreviewPin(null))
        }
    }
    this.clickOffPin = async () => {
      const { openPin: e } = this.viewData
      e && this.deselectPin(e)
    }
    this.movePin = async e => {
      const { openPin: t, isPinEditable: i } = this.viewData
      i &&
        (t && t.id === e.id
          ? (this.viewData.updateOpenPin(e.pos), this.engine.broadcast(new PinMovedMessage(t.id, t.pinType, e.pos)))
          : this.log.debug("Cannot move the pin, not open for edit"))
    }
    this.placePin = async e => {
      const { openPin: t, isPinEditable: i, canPlace: n } = this.viewData
      i &&
        (t && n
          ? (this.viewData.setPinState(pinStateEnum.EDITING), this.engine.broadcast(new PinPlacedMessage(t.id, t.pinType, t)))
          : (this.log.debug("Cannot place pin because there is no open pin"), this.cancelAndReset()))
    }
  }

  async init(e, t: EngineContext) {
    this.engine = t
    this.tagsEnabled = e.newTagsEnabled
    const [InputIni, WebGLRenderer, s, a, l] = await Promise.all([
      t.getModuleBySymbol<InputIniModule>(SymbolList.INPUT),
      t.getModuleBySymbol<WebGLRendererModule>(SymbolList.WEBGL_RENDERER),
      t.market.waitForData(CanvasData),
      t.market.waitForData(ApplicationData),
      t.getModule(RaycasterModule)
    ])
    this.input = InputIni
    const Layer = t.claimRenderLayer(this.name)
    this.pinRenderer = WebGLRenderer.supportsInstancing()
      ? new InstancedPinRenderer(InputIni, WebGLRenderer.getCamera(), s, Layer, t.commandBinder, l)
      : new NonInstancedPinRenderer(InputIni, WebGLRenderer.getCamera(), s, Layer, t.commandBinder, l)
    WebGLRenderer.getScene().add(this.pinRenderer.container)
    t.addComponent(this, this.pinRenderer)
    ;[this.floorsViewData, this.viewmodeData, this.interactionmodeData, this.sweepData, this.cameraData] = await Promise.all([
      t.market.waitForData(FloorsViewData),
      t.market.waitForData(ViewmodeData),
      t.market.waitForData(InteractionmodeData),
      t.market.waitForData(SweepData),
      t.market.waitForData(CameraData)
    ])
    this.viewData = new PinsViewData()
    this.pinEditor = new PinEditor(this.viewData, this.engine, this.input, this.pinRenderer)
    this.floorsViewData.iterate(e => this.pinRenderer.getFloorContainer(e.id))
    this.viewmode = this.viewmodeData.currentMode
    this.bindings.push(
      t.commandBinder.addBinding(EnablePinEditingCommand, this.onEnablePinEditing),
      t.commandBinder.addBinding(OpenPinCommand, this.onOpenPin),
      t.commandBinder.addBinding(SelectPinCommand, this.onSelectPin),
      t.commandBinder.addBinding(ClickAwayFromPinCommand, this.clickOffPin),
      this.interactionmodeData.onChanged(this.visibilityChanged),
      this.floorsViewData.onChanged(this.visibilityChanged),
      this.viewmodeData.makeModeChangeSubscription(this.visibilityChanged),
      t.subscribe(MoveToSweepMessage, this.visibilityChanged),
      t.subscribe(SwitchViewmodeMessage, this.viewmodeChanged),
      t.subscribe(EndSwitchViewmodeMessage, this.viewmodeChanged),
      t.subscribe(PinClickedMessage, this.onPinClicked),
      t.subscribe(PinOpenedMessage, this.onPinOpened),
      t.subscribe(PinSelectedMessage, this.onPinSelected),
      t.subscribe(PinUpdatedMessage, this.onPinUpdated),
      t.subscribe(PinClosedMessage, this.onPinClosed),
      t.subscribe(ToolAssetOpenedMessage, this.onAssetOpened),
      t.subscribe(ToolToggledMessage, this.onToolToggled),
      t.subscribe(ApplicationChangeMessage, this.applicationChanged),
      this.viewData.onPinStateChanged(this.updateRenderedPin),
      this.viewData.onOpenPinChanged(this.updateRenderedPin),
      this.viewData.onPreviewPinChanged(this.updateRenderedPin)
    )
    if (!this.touchDevice) {
      this.bindings.push(
        t.commandBinder.addBinding(PreviewPinCommand, this.onPreviewPinAnnotation),
        t.subscribe(PinHoverChangeMessage, this.onHoverChanged),
        this.cameraData.onChanged(this.onCameraUpdate)
      )
    }
    let v = a.application
    this.bindings.push(
      a.onPropertyChanged("application", (e: string) => {
        e !== v && (this.visibilityChanged(), (v = e))
      })
    )
    this.visibilityChanged()
    t.market.register(this, PinsViewData, this.viewData)
  }
  dispose(e) {
    this.disableEditing()
    this.viewData.setPinState(pinStateEnum.IDLE)
    this.pinEditor.dispose()
    this.pinRenderer.dispose()
    this.bindings.forEach(e => {
      e.cancel()
    })
    this.bindings = []
    this.editBindings = []
    super.dispose(e)
  }
  onUpdate() {
    this.pinEditor.update()
  }
  in360View() {
    const e = this.sweepData.currentSweep ? this.sweepData.currentSweep : ""
    return this.viewmodeData.isInside() && this.sweepData.isSweepUnaligned(e)
  }
  enableEditing() {
    if (!this.editActivated) {
      if (!this.editBindings.length) {
        const e = this.engine
        const t = e.commandBinder
        this.editBindings.push(
          e.subscribe(PinStartCreatingMessage, this.onStartPinCreation),
          e.subscribe(PinDiscardedMessage, this.cancelAndReset),
          e.subscribe(PinDeletedMessage, this.onPinDeleted),
          t.addBinding(AdjustPinStemLengthCommand, this.onPreviewStemLength),
          t.addBinding(PlacePinCommand, this.placePin),
          t.addBinding(CancelNewPinCommand, this.cancelPinCreation),
          t.addBinding(MovePinCommand, this.movePin),
          this.sweepData.makeSweepChangeSubscription(this.handleSweepChange),
          e.subscribe(EndSwitchViewmodeMessage, this.handleViewModeChange)
        )
      } else {
        this.editBindings.forEach(e => {
          e.renew()
        })
      }
      this.handleSweepAndViewModeChange()
      this.editActivated = !0
    }
  }
  disableEditing() {
    if (this.editActivated) {
      this.cancelAndReset()
      this.viewData.setPinState(pinStateEnum.IDLE)
      this.editBindings.forEach(e => {
        e.cancel()
      })
      this.editActivated = !1
    }
  }
  handleSweepAndViewModeChange() {
    const e = this.viewData
    const t = !this.in360View()
    e.creatingNewPin && !t ? this.cancelAndReset() : e.setCanAdd(t)
  }
  selectPin(id: string, t: string, transition?: number) {
    if (t === pinTypeEnum.MATTERTAG) {
      const { openPin } = this.viewData
      ;(id === openPin?.id && t === openPin?.pinType) || this.engine.commandBinder.issueCommand(new SelectTagCommand(id, transition))
    }
  }
  async openPin(id: string, t: string, transition?: number, attachmentId?: string, listIndex = -1) {
    this.viewData.creatingNewPin && (await this.engine.commandBinder.issueCommand(new CancelNewPinCommand()))
    return t === pinTypeEnum.NOTE
      ? this.engine.commandBinder.issueCommand(new OpenNoteCommand(id, transition, attachmentId, listIndex))
      : t === pinTypeEnum.MATTERTAG
      ? this.engine.commandBinder.issueCommand(new OpenTagCommand(id, transition, attachmentId, listIndex))
      : undefined
  }
  deselectPin(e) {
    this.engine.broadcast(new PinDeselectMessage(e.id, e.pinType))
  }
}
